/*******************************************************************************
 * Copyright (c) 2014 Steffen Mikkelsen
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/gpl.html
 * 
 * Contributors:
 *     Steffen Mikkelsen - initial API and implementation
 ******************************************************************************/
package com.subsecret.secureconnection.securestream.stream.obfuscation;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Random;

public class ObfuscationInputStream extends InputStream {
    
    private InputStream is;
    private byte[] decryptionTable;
    
    public ObfuscationInputStream(InputStream is, byte[] secret) {
        this.is = is;
        decryptionTable = byteDecryptionTable(secret);
    }
    
    @Override
    public long skip(long n) throws IOException {
        return is.skip(n);
    }
    
    static byte[] getByteMapping(byte[] secret) {
        byte[] table = new byte[256];
        int randSeed = 0;
        for (int i = 0; i < secret.length; i++) {
            randSeed += secret[i];
        }
        Random rand = new Random(randSeed);
        HashSet<Byte> used = new HashSet<Byte>();
        for (int i = 0; i < table.length; i++) {
            
            byte rndByte;
            
            do {
                rndByte = (byte) (rand.nextInt(256) - 128);
            }
            while (used.contains(rndByte));
            
            used.add(rndByte);
            table[i] = rndByte;
        }
        return table;
    }
    
    public static byte[] byteDecryptionTable(byte[] secret) {
        byte[] byteMapping = getByteMapping(secret);
        byte[] table = new byte[256];
        for (int i = 0; i < table.length; i++) {
            int tIndex = byteMapping[i] + 128;
            byte val = (byte) (i - 128);
            table[tIndex] = val;
        }
        return table;
    }
    
    @Override
    public synchronized void reset() throws IOException {
        is.reset();
    }
    
    @Override
    public int available() throws IOException {
        return is.available();
    }
    
    @Override
    public boolean markSupported() {
        return is.markSupported();
    }
    
    @Override
    public synchronized void mark(int readlimit) {
        is.mark(readlimit);
    }
    
    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        int read = is.read(b, off, len);
        for (int i = 0; i < len; i++) {
            byte val = b[i + off];
            b[i + off] = decryptionTable[val + 128];
        }
        return read;
    }
    
    @Override
    public int read(byte[] b) throws IOException {
        return read(b, 0, b.length);
    }
    
    @Override
    public void close() throws IOException {
        is.close();
    }
    
    private final byte[] singleByte = new byte[1];
    
    @Override
    public int read() throws IOException {
        int read = read(singleByte, 0, 1);
        if (read == -1) {
            return read;
        }
        return singleByte[0] & 0xff;
    }
}
